/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementUtil;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import static org.apache.ibatis.executor.ExecutionPlaceholder.EXECUTION_PLACEHOLDER;

/**
 * @author Clinton Begin
 */
public abstract class BaseExecutor implements Executor {

    private static final Log log = LogFactory.getLog(BaseExecutor.class);
    /**
     * JDBC链接的包装器
     */
    protected Transaction transaction;
    /**
     * SQL执行器
     */
    protected Executor wrapper;

    protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
    /**
     * 本地缓存
     */
    protected PerpetualCache localCache;
    /**
     * 本地输出参数缓存
     */
    protected PerpetualCache localOutputParameterCache;
    /**
     * Mybatis全局配置
     */
    protected Configuration configuration;

    /**
     * 查询栈计数器
     */
    protected int queryStack;
    /**
     * 当前连接是否已关闭
     */
    private boolean closed;

    protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<>();
        this.localCache = new PerpetualCache("LocalCache");
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        this.closed = false;
        this.configuration = configuration;
        this.wrapper = this;
    }

    @Override
    public Transaction getTransaction() {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        return transaction;
    }

    @Override
    public void close(boolean forceRollback) {
        try {
            try {
                rollback(forceRollback);
            } finally {
                if (transaction != null) {
                    transaction.close();
                }
            }
        } catch (SQLException e) {
            // Ignore.  There's nothing that can be done at this point.
            log.warn("Unexpected exception on closing transaction.  Cause: " + e);
        } finally {
            transaction = null;
            deferredLoads = null;
            localCache = null;
            localOutputParameterCache = null;
            closed = true;
        }
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 清理缓存
        clearLocalCache();
        // 执行更新操作
        return doUpdate(ms, parameter);
    }

    @Override
    public List<BatchResult> flushStatements() throws SQLException {
        return flushStatements(false);
    }

    public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        return doFlushStatements(isRollBack);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取可执行Sql对象
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 构建缓存Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            // 需要刷新缓存且非嵌套查询
            clearLocalCache();
        }
        // 返回结果对象
        List<E> list;
        try {
            // 查询栈++ 或者叫做查询计数器
            queryStack++;
            // 获取本地缓存的结果，这里操作的是一级缓存，因为直接耦合在代码内，所以一级缓存无法禁用
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                // 如果命中本地缓存，从缓存中取值
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 未命中缓存,从数据库获取内容，并尝试将其缓存起来
                // 其实可以把缓存的操作放在这里，更容易联系上下文
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            // 操作完成,恢复查询栈(恢复查询计数器)
            queryStack--;
        }

        if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
                // 加载懒加载数据
                deferredLoad.load();
            }

            // issue #601
            deferredLoads.clear();
            // 根据缓存的声明周期决定是否释放缓存
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // 如果当前缓存的生命周期是一次声明语句，清理缓存
                // issue #482
                clearLocalCache();
            }
        }
        // 返回查询结果
        return list;
    }

    @Override
    public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return doQueryCursor(ms, parameter, rowBounds, boundSql);
    }

    @Override
    public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
        if (deferredLoad.canLoad()) {
            deferredLoad.load();
        } else {
            deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
        }
    }

    /**
     * 生成一个缓存Key
     *
     * @param ms              声明语句
     * @param parameterObject 入参对象
     * @param rowBounds       分页数据
     * @param boundSql        SQL
     * @return 缓存Key
     */
    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 构成一个缓存Key
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        cacheKey.update(boundSql.getSql());

        // 获取Sql节点中的参数定义
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        // 获取类型处理注册表
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
            // 跳过参数列表中的出参
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                // 获取对应的字段名称
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    // 从动态生成的参数列表中获取参数
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    // 没有入参
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    // 通过类型处理器获取参数
                    value = parameterObject;
                } else {
                    // 为入参对象生成对象元数据
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    // 获取对应的值
                    value = metaObject.getValue(propertyName);
                }
                // 更新缓存key对参数值的维护
                cacheKey.update(value);
            }
        }
        if (configuration.getEnvironment() != null) {
            // 如果定义了环境，维护环境对象
            // issue #176
            cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
    }

    @Override
    public boolean isCached(MappedStatement ms, CacheKey key) {
        return localCache.getObject(key) != null;
    }

    @Override
    public void commit(boolean required) throws SQLException {
        if (closed) {
            throw new ExecutorException("Cannot commit, transaction is already closed");
        }
        clearLocalCache();
        flushStatements();
        if (required) {
            transaction.commit();
        }
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        if (!closed) {
            try {
                clearLocalCache();
                flushStatements(true);
            } finally {
                if (required) {
                    transaction.rollback();
                }
            }
        }
    }

    /**
     * 清除本地缓存
     */
    @Override
    public void clearLocalCache() {
        if (!closed) {
            // 清除本地缓存
            localCache.clear();
            localOutputParameterCache.clear();
        }
    }

    protected abstract int doUpdate(MappedStatement ms, Object parameter)
            throws SQLException;

    protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
            throws SQLException;

    /**
     * 列表查询
     *
     * @param ms            声明语句
     * @param parameter     入参
     * @param rowBounds     分页数据
     * @param resultHandler 返回结果处理器
     * @param boundSql      SQL实体
     * @param <E>           返回类型
     * @return 查询出的列表
     * @throws SQLException SQL执行异常
     */
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
            throws SQLException;

    protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
            throws SQLException;

    protected void closeStatement(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                // ignore
            }
        }
    }

    /**
     * Apply a transaction timeout.
     *
     * @param statement a current statement
     * @throws SQLException if a database access error occurs, this method is called on a closed <code>Statement</code>
     * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
     * @since 3.4.0
     */
    protected void applyTransactionTimeout(Statement statement) throws SQLException {
        StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
    }

    /**
     * 处理本地化缓存的输出参数
     *
     * @param ms        声明语句
     * @param key       缓存Key
     * @param parameter 入参
     * @param boundSql  SQL对象
     */
    private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
        // 只处理存储过程和函数调用的出参
        // ,因为存储过程和函数的返回不是通过ResultMap而是通过ParameterMap.
        // 所以只要把缓存的非IN模式参数取出来设置到parameter对应的属性上即可.
        if (ms.getStatementType() == StatementType.CALLABLE) {
            // 处理存储过程，在本地输出缓存中获取该缓存内容
            final Object cachedParameter = localOutputParameterCache.getObject(key);
            if (cachedParameter != null && parameter != null) {
                // 获取缓存对象的对象元数据
                final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
                // 获取入参对象的对象元数据
                final MetaObject metaParameter = configuration.newMetaObject(parameter);
                for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                    // 遍历处理每一个入参定义
                    if (parameterMapping.getMode() != ParameterMode.IN) {
                        // 处理输出参数定义
                        // 获取字段名称
                        final String parameterName = parameterMapping.getProperty();
                        // 获取缓存中存储的值
                        final Object cachedValue = metaCachedParameter.getValue(parameterName);
                        // 设值
                        metaParameter.setValue(parameterName, cachedValue);
                    }
                }
            }
        }
    }

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 设置本地缓存占位符，表示该查询将被缓存
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {

            //查询 doQuery方法抽象方法，交由子类去实现，刚好对应ExecutorType的枚举实例
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 释放占位符
            localCache.removeObject(key);
        }
        // 真正加入缓存
        localCache.putObject(key, list);

        if (ms.getStatementType() == StatementType.CALLABLE) {
            // 处理存储过程
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

    /**
     * 获取链接
     *
     * @param statementLog 日志实现
     * @return 链接
     * @throws SQLException SQL异常
     */
    protected Connection getConnection(Log statementLog) throws SQLException {
        // 获取一个JDBC链接
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
            // 返回一个具有日志功能的链接
            return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
            // 返回链接
            return connection;
        }
    }

    @Override
    public void setExecutorWrapper(Executor wrapper) {
        this.wrapper = wrapper;
    }

    private static class DeferredLoad {

        private final MetaObject resultObject;
        private final String property;
        private final Class<?> targetType;
        private final CacheKey key;
        private final PerpetualCache localCache;
        private final ObjectFactory objectFactory;
        private final ResultExtractor resultExtractor;

        // issue #781
        public DeferredLoad(MetaObject resultObject,
                            String property,
                            CacheKey key,
                            PerpetualCache localCache,
                            Configuration configuration,
                            Class<?> targetType) {
            this.resultObject = resultObject;
            this.property = property;
            this.key = key;
            this.localCache = localCache;
            this.objectFactory = configuration.getObjectFactory();
            this.resultExtractor = new ResultExtractor(configuration, objectFactory);
            this.targetType = targetType;
        }

        public boolean canLoad() {
            return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
        }

        /**
         * 加载懒加载对象
         */
        public void load() {
            @SuppressWarnings("unchecked")
            // we suppose we get back a List

                    List<Object> list = (List<Object>) localCache.getObject(key);
            Object value = resultExtractor.extractObjectFromList(list, targetType);
            // 设值
            resultObject.setValue(property, value);
        }

    }

}
